package br.ufrgs.rescue.agent;

import br.ufrgs.rescue.model.BuildingOnFire;
import br.ufrgs.rescue.model.Channel;
import br.ufrgs.rescue.model.State;
import br.ufrgs.rescue.util.BuildingOnFireComparator;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import static rescuecore2.misc.Handy.objectsToIDs;

import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Random;
import java.util.logging.Level;

import rescuecore2.worldmodel.EntityID;
import rescuecore2.worldmodel.ChangeSet;
import rescuecore2.messages.Command;
import rescuecore2.log.Logger;

import rescuecore2.standard.entities.StandardEntity;
import rescuecore2.standard.entities.StandardEntityURN;
import rescuecore2.standard.entities.Building;
import rescuecore2.standard.entities.Refuge;
import rescuecore2.standard.entities.FireBrigade;
import sample.DistanceSorter;

/**
 * A fire brigade agent developed by Ricardo Grunitzki.
 */
public class MyFireBrigade_Treinamento extends MyAbstractAgent<FireBrigade> {

    private static final String MAX_WATER_KEY = "fire.tank.maximum";
    private static final String MAX_DISTANCE_KEY = "fire.extinguish.max-distance";
    private static final String MAX_POWER_KEY = "fire.extinguish.max-sum";
    private static final String MAX_VIEW_KEY = "perception.los.max-distance";
    private int maxWater;
    private int maxDistance;
    private int maxPower;
    private int maxView;
    private String test = "teste.txt";
    State estado = State.FINDING;
    private List<Integer> notifiedFires = new ArrayList<>();

    @Override
    public String toString() {
        return "my fire brigade";
    }

    @Override
    protected void postConnect() {
        super.postConnect();
        model.indexClass(StandardEntityURN.BUILDING, StandardEntityURN.REFUGE);
        maxWater = config.getIntValue(MAX_WATER_KEY);
        maxDistance = config.getIntValue(MAX_DISTANCE_KEY);
        maxPower = config.getIntValue(MAX_POWER_KEY);
        maxView = config.getIntValue(MAX_VIEW_KEY);
        Logger.info("My fire brigade connected: max extinguish distance = " + maxDistance + ", max power = " + maxPower + ", max tank = " + maxWater);

        try {
            salvar_resultados("\tcomposição\tfieryness\ttemperature\ttotalArea\tbrokenness\tfloors\tgroundArea\tignition\timportance\tt_inicial\tt_final", test);
//            salvar_resultados("ID\tbrokenness\tfieryness\ttemp\ttotalArea\tmaxPower\ttime", test);
        } catch (IOException ex) {
            java.util.logging.Logger.getLogger(MyFireBrigade.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @Override
    protected void think(int time, ChangeSet changed, Collection<Command> heard) {
        System.out.print(me().getID());

        if (time == config.getIntValue(kernel.KernelConstants.IGNORE_AGENT_COMMANDS_KEY)) {
            // Subscribe to channel 1
            sendSubscribe(time, Channel.FIRE_BRIGADE.ordinal());
        }
        System.out.print("\touviu");
        //ouvir canal de comunicação
        heardMessage(heard);
        System.out.print("\tpercebeu");
        //perceber ambiente

        //verifica se está vendo algum prédio em chamas
        //atualizar lista de alvos
        System.out.print("\t" + "predios em chamas?");
        Collection<EntityID> all = getBurningBuildings();
        for (EntityID next : all) {
            //Mantem a lista de prédios em chamas
            BuildingOnFire b = new BuildingOnFire(buildingById(next));
            insertBuilding(b);
//            System.out.print("\tencontrou alvo(s)");
            if (!notifiedFires.contains(b.getId().getValue())) {
                sendMesssage(time, Channel.FIRE_BRIGADE.ordinal(), b);
                notifiedFires.add(b.getId().getValue());
            }

        }
        System.out.print("\t alvos = " + buildingsOnFire.size());

        switch (estado) {
            case CHARGING:
                System.out.print("\t" + estado.toString());
                // testa se esta em refugio colocando agua
                System.out.print("\t" + "estou refugio?");
                if (location() instanceof Refuge) {
                    System.out.print("\t" + "encheu?");
                    if (me().isWaterDefined() && me().getWater() < maxWater && location() instanceof Refuge) {
                        update_waterBuilding(current_target, maxPower, time);
                        //sendRest(time);
                        System.out.print("\t" + "agua: " + me().getWater());
                        break;
                    } else {
                        System.out.print("\t" + "já encheu, vou apagar fogo");
                        estado = State.EXTINGUISHING;
                    }
                } else {
                    System.out.print("\t não estou no refugio");
                    goToRefuge(time);
                    break;
                }
            case EXTINGUISHING:
                System.out.print("\t" + estado.toString());
                System.out.print("\talvo?");
                //tenho um alvo?
                if (current_target != null) {
                    System.out.print("\tapagar daqui?");
                    //posso apagar daqui?
                    if (canIExtinguishToHere(current_target)) {
                        System.out.print("\ttenho agua?");
                        // tenho agua?
                        if (isThereWater()) {
                            System.out.print("\ttá queimando?");
                            //verificar se ainda está queimando
                            if (isOnFire(current_target)) {
                                System.out.print("\tsim");
                                System.out.print("\tposso Ver?");
                                if (isVisible(current_target)) {
                                    //continua queimando?
                                    if (isOnFire(current_target)) {
                                        update_waterBuilding(current_target, maxPower, time);
                                        //sendExtinguish(time, current_target, maxPower);
                                        //sendRest(time);
                                    } else {
                                        try {
                                            current_target = buildingToExtinguish(time);
                                        } catch (IOException ex) {
                                            java.util.logging.Logger.getLogger(MyFireBrigade_Treinamento.class.getName()).log(Level.SEVERE, null, ex);
                                        }
                                    }
                                } else {
                                    System.out.print("\tnão, aproximar");
                                    goToFire(time, current_target);
//                                    sendMove(time, goToFire(current_target));
                                }
                                break;
                            } else {
                                System.out.print("\tnão!");
                            }
                        } else {
                            System.out.print("\ta água acabou, vou para o refúgio");
                            //a água acabou, vou para o refúgio;
                            estado = State.CHARGING;
                            goToRefuge(time);
                            break;
                        }
                    } else {
                        System.out.print("\taproximar do fogo");
                        //estou muito longe, vou caminhar até o alvo
                        goToFire(time, current_target);
//                        sendMove(time, goToFire(current_target));
                        break;
                    }
                } else {
                    System.out.print("\tnao tenho avo");
                    //não tenho alvo, vou procurar
                    estado = State.FINDING;
                }
            case FINDING:
                System.out.print("\t" + estado.toString());
                estado = State.FINDING;
                System.out.print("\t" + "atualiza o alvo");
                try {
                    //atualizar o alvo
                    current_target = buildingToExtinguish(time);
                } catch (IOException ex) {
                    java.util.logging.Logger.getLogger(MyFireBrigade_Treinamento.class.getName()).log(Level.SEVERE, null, ex);
                }
                System.out.print("\t" + "tenho um alvo?");
                if (current_target != null) {
                    //posso apagar daqui?
                    System.out.print("\t" + "posso apagar daqui?");
                    if (canIExtinguishToHere(current_target)) {
                        if (isOnFire(current_target)) {
                            if (isVisible(current_target)) {
                                update_waterBuilding(current_target, maxPower, time);
                                //sendExtinguish(time, current_target, maxPower);
                                //sendRest(time);
                            }
                        } else {
                            try {
                                current_target = buildingToExtinguish(time);
                            } catch (IOException ex) {
                                java.util.logging.Logger.getLogger(MyFireBrigade_Treinamento.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }
                    } else {
                        System.out.print("\t" + "caminhar até o prédio");
                        //ir até o fogo
                        goToFire(time, current_target);
//                        sendMove(time, goToFire(current_target));
                    }

                    System.out.print("\t" + "status = extinguishing");
                    estado = State.EXTINGUISHING;
                } else {
                    System.out.print("\t" + "não tenho um alvo, randonWalk()");
                    goToFire(time, null);
//                    sendMove(time, goToFire(null));
                }
                break;
        }
        System.out.println("");
    }

    @Override
    protected EnumSet<StandardEntityURN> getRequestedEntityURNsEnum() {
        return EnumSet.of(StandardEntityURN.FIRE_BRIGADE);
    }

    private Collection<EntityID> getBurningBuildings() {
//        Collection<StandardEntity> e = model.getEntitiesOfType(StandardEntityURN.BUILDING);
        Collection<StandardEntity> e = model.getObjectsInRange(location(), maxView);
        List<Building> result = new ArrayList<Building>();
        for (StandardEntity next : e) {
            if (next instanceof Building) {
                Building b = (Building) next;
                if (b.isOnFire() && b.getTemperature() > 40 && b.getFieryness() < 4) {
                    result.add(b);
                }
            }
        }
        // Sort by distance
        Collections.sort(result, new DistanceSorter(location(), model));
        return objectsToIDs(result);
    }

    private boolean isVisible(EntityID id) {
        Collection<StandardEntity> entitys = model.getObjectsInRange(location(), maxView);
        for (StandardEntity ent : entitys) {
            if (ent.getID().getValue() == id.getValue()) {
                return true;
            }
        }
        return false;
    }

    private List<EntityID> planPathToFire(EntityID target) {
        // Try to get to anything within maxDistance of the target
        Collection<StandardEntity> targets = model.getObjectsInRange(target, maxView);
        if (targets.isEmpty()) {
            return null;
        }
        return search.breadthFirstSearch(me().getPosition(), objectsToIDs(targets));
    }

    private void salvar_resultados(String s, String file_name) throws IOException {
        File saida = new File("/home/gauss/rgrunitzki/logs", file_name);
        FileWriter writer = new FileWriter(saida, true);
        PrintWriter printer = new PrintWriter(writer);
        printer.println(s);
        printer.flush();
        writer.close();
    }

    private Building buildingById(EntityID id) {
        Building b = (Building) model.getEntity(id);
        return b;
    }

    private void removeBuildOnFire(EntityID entityID) {
        System.out.print("\tEntrou no removeBuildOnFire");
        if (entityID != null) {
            Building building = buildingById(entityID);
            int i;
            for (i = 0; i < buildingsOnFire.size(); i++) {
                if (buildingsOnFire.get(i).getId().getValue() == building.getID().getValue()) {
                    buildingsOnFire.remove(i);
                    System.out.print("\tremoveu um onFire");
                    return;
                }
            }
        } else {
            System.out.print("\tnão tinha alvo anterior");
        }
    }
    int initial_time = 0;

    private EntityID buildingToExtinguish(int time) throws IOException {

        if (current_target != null) {
            gravaDadosTreinamento((current_target), time);
        }
        removeBuildOnFire(current_target);
        Collections.sort(buildingsOnFire, new BuildingOnFireComparator());
        initial_time = 0;
        if (buildingsOnFire.size() > 0) {
            return buildingsOnFire.get(0).getId();
        }
        return null;
    }

    private boolean canIExtinguishToHere(EntityID entityID) {
        if (model.getDistance(getID(), entityID) <= maxView) {
            return true;
        } else {
            return false;
        }
    }

    private void goToFire(int time, EntityID entityId) {
        if (entityId == null) {
            sendMove(time, randomWalk_2());
//            sendMove(time, randomWalkonStreet());
            return;
        }
//        List<EntityID> path = planPathToFire(entityId);
        List<EntityID> path = search.breadthFirstSearch(me().getPosition(), buildingById(entityId).getNeighbours());
        if (path != null) {
            System.out.println("");
            System.out.println("tamanho do alvo: " + path.size() + " - destino: " + path.get(path.size() - 1).getValue());
            System.out.println("");
            sendMove(time, path);
            return;
        } else {
//            sendMove(time, randomWalkonStreet());
            sendMove(time, randomWalk_2());

        }
    }

    private boolean isThereWater() {
        if (me().getWater() > 0) {
            return true;
        } else {
            return false;
        }
    }

    private void goToRefuge(int time) {
        List<EntityID> path = search.breadthFirstSearch(me().getPosition(), refugeIDs);
        if (path != null) {
            sendMove(time, path);
        } else {
            path = randomWalk_2();
//            path = randomWalk();
            sendMove(time, path);
        }
    }

    private boolean isOnFire(EntityID entityID) {
        if (buildingById(entityID).isOnFire() && buildingById(entityID).getTemperature() > 40 && buildingById(entityID).getFieryness() < 4) {
            return true;
        } else {
            return false;
        }
    }

    protected List<EntityID> randomWalk_2() {
        return search.breadthFirstSearch(me().getPosition(), roadIDs.get(new Random().nextInt(roadIDs.size())));
    }

    //Métodos para gerar arquivo de treinamento:
    public void gravaDadosTreinamento(EntityID id, int time) throws IOException {
        BuildingOnFire b = null;
        BuildingOnFire b2 = null;
        for (BuildingOnFire build : buildingsOnFire) {
            if (build.getId().getValue() == id.getValue()) {
                b = build;
            }
        }
        for (BuildingOnFire build : buildingsToPrint) {
            if (build.getId().getValue() == id.getValue()) {
                b2 = build;
            }
        }

        File saida = new File("/home/gauss/rgrunitzki/logs", "arquivo_treinamento");
        FileWriter writer = new FileWriter(saida, true);
        PrintWriter printer = new PrintWriter(writer);
        if (b != null) {
            //composição, fieryness, temperature, totalArea, brokenness, floors, groundArea, ignition, importance
            printer.println(b.getComposition() + "\t" + b.getFieryness() + "\t" + b.getTemperature() + "\t" + b.getTotalArea()
                    + "\t" + buildingById(id).getBrokenness() + "\t" + buildingById(id).getFloors() + "\t" + buildingById(id).getGroundArea()
                    + "\t" + "\t" + buildingById(id).getImportance() + "\t" + initial_time + "\t" + time);
        } else {
            printer.println("prédio inválido em: " + id.getValue());
        }
        printer.flush();
        writer.close();

    }

    public void update_waterBuilding(EntityID id, int water, int time) {
        for (int i = 0; i < buildingsOnFire.size(); i++) {
            if (buildingsOnFire.get(i).getId().getValue() == id.getValue()) {
                System.out.println("");
                System.out.print("atualizou a água" + buildingsOnFire.get(i).getWater() + " -  ");
                buildingsOnFire.get(i).setWater(buildingsOnFire.get(i).getWater() + water);
                System.out.println(buildingsOnFire.get(i).getWater());
                if (initial_time == 0) {
                    initial_time = time;
                }
                return;
            }
        }
        System.out.println("");
        System.out.println("");
        System.out.println("");
        System.out.println("NÃO ENCONTROU PRÉDIO PARA ATUALIZAR A ÁGUA NO UPDATE_WATERbUILDING(XXX)");
        System.out.println("");
        System.out.println("");
        System.out.println("");
    }
}